/*
* Copyright (c) 2012-2013 by Justin Jensen <justin@gravitygame.hostingsiteforfree.com>
*
* All rights reserved. No warranty, explicit or implicit, provided.
*
*/

var c;
var ctx;
		
var startBodyCount = Math.floor(random(500,600));
var G = 0.1;
var scale = 1;	//Scale = world size / screen size

var scnOffset = new Vector(0,0);

var bodies = [];
var scWidth;
var scHeight;
var viewWidth;
var viewHeight;
var infoBarHeight = 0;
var trailLength = 100;

var showTrails = true;
var paused = false;
var dragging = false;
var panning = true;
var mousePressed = false;
var showMotionVector = false;

var draggedPlanet = new Body();
var dragStartX, dragStartY, dragEndX, dragEndY;	//world
var lastE; //previous mouse move event while dragging

var mouseX; //screen
var mouseY; //screen
var newPlanetMass = 200;
var newPlanetRadius = Math.pow((Math.abs(newPlanetMass) * 3 / (4 * Math.PI)), 0.3333);

var isRecording = false;
var showRecordingText = false;
var gameTime = 0;
var recording;

document.onkeypress = function(e){
	var charCode = (e.which)? e.which : event.keyCode;
	
	//d - 100
	//p - 112
	//r - 114
	//c - 99
	//s - 115
	//up - 38
	//down - 40
	switch(charCode){
		case 100:		//d
			bodies.push(new Body(40000, viewWidth/2, viewHeight/2));
			break;
		case 112:		//p
			togglePause();
			break;
		case 114:		//r
			seedScreen();
			break;
		case 99:		//c
			clearScreen();
			break;
		case 115:		//s
			alert('saved');
			//saveFrame();
			break;
	}
}

var saveGame;
var loadGame;

window.onresize = function(){
	updateCanvasSize();
}

function updateCanvasSize(){
	scWidth = document.documentElement.clientWidth - 110;
	scHeight = document.documentElement.clientHeight - 120;
	document.getElementById('playArea').width = scWidth;
	document.getElementById('playArea').height = scHeight;
	viewWidth = scWidth;
	viewHeight = scHeight - infoBarHeight;
}

window.onload = function(){

	//
	/*document.getElementById('playArea').onmousedown = function(event){
		event.preventDefault();
	};*/
	
	//scWidth = 1000;
	//scHeight = 800;
	updateCanvasSize();
	
	//viewWidth = scWidth;
	//viewHeight = scHeight - infoBarHeight;
	
	c = document.getElementById("playArea");
	ctx = c.getContext("2d");
	
	//check for local storage
	if(typeof(Storage) !== "undefined"){
		//create save button
		document.getElementById("saveLoadButtons").innerHTML = '<td><button id="saveButton" type="button" onclick="saveGame();">save</button><button id="loadButton" type="button" onclick="loadGame();">load</button></td><td colspan="3"></td>';
		//create save function
		saveGame = function(){
			var savedGame = JSON.stringify(bodies.map(function(body){
				return{
					trail: [],
					mass: body.mass,
					x: body.x,
					y: body.y,
					motion: body.motion,
					width: body.width,
					bodyColor: body.bodyColor
				};
			}));
			//console.log(savedGame);
			localStorage.ggame = savedGame;
			//alert that the game's been saved
		};
		//create load button
		//create load function
		loadGame = function(){
			if(!localStorage.ggame){
				alert("No game saved");
				return;
			}
			//pause the game
			setPause(true);
			//clear the bodies array
			bodies = [];
			//JSON decode the save game string
			tempBodies = JSON.parse(localStorage.ggame);
			//replace the bodies array
			for(i = 0; i < tempBodies.length; i++){
				bodies.push(new Body(tempBodies[i]));
			}
		}
		redraw();
	}

	function redraw(){
		//copy in some global variables
		var _viewHeight = viewHeight, _viewWidth = viewWidth;
		var bgColor = "#000000";
		var bodyColor = "#888888";
		var offScreenColor = "#EA4B00";
		var trailColor = "#555555";
		var offScreenOutline = "#555555";
		var _showTrails = showTrails;
		var _scale = scale;
		var motionColor = "#CC4444";
		var _showMotionVector = showMotionVector;
		
		//clear the screen
		ctx.fillStyle = bgColor;
		ctx.fillRect(0,0,scWidth,scHeight);
		
		var TWOPI = Math.PI * 2;
		
		if(!paused){
			//if recording
			if(isRecording){
				//increment the timestamp
				gameTime++;
			}
			updateBodiesAcc();
			updateBodiesPos();
			resolveCollisions();
		}
		
		var i = bodies.length;
		while(i--){
			var I = bodies[i];
			var planetRad = (I.width/2)*_scale;
			if(_showTrails){
				//draw the planet's trail
				ctx.strokeStyle = trailColor;
				ctx.beginPath();
				for(b = 0, cLen = I.trail.length-1; b < cLen; b++){					
					ctx.moveTo(getScX(I.trail[b].x), getScY(I.trail[b].y));
					ctx.lineTo(getScX(I.trail[b+1].x), getScY(I.trail[b+1].y));					
				}				
				ctx.stroke();
			}
			var nX = I.getScX();
			var nY = I.getScY();
			//if the planet is completely off the screen
			if(nX - planetRad > _viewWidth || nX + planetRad < 0 || nY - planetRad > _viewHeight || nY + planetRad < 0){
				//to-do: don't draw a circle in the corner if there's already one there
				
				//var nX = I.getScX();
				if(nX > _viewWidth){
					nX = _viewWidth - 5;
				}
				else if(nX < 0){
					nX = 5;
				}
				
				if(nY > _viewHeight){
					nY = _viewHeight - 5;
				}
				else if(nY < 0){
					nY = 5;
				}
				//draw red circle
				ctx.beginPath();
				ctx.arc(nX, nY, 2.5, 0, TWOPI);
				ctx.closePath();
				ctx.fillStyle = offScreenColor;
				ctx.fill();
				ctx.strokeStyle = offScreenOutline;
				ctx.stroke();
			}
			else{
				//draw planet
				ctx.beginPath();
				ctx.arc(nX, nY, (I.width/2)*_scale, 0, TWOPI);
				ctx.closePath();
				ctx.fillStyle = I.bodyColor;
				ctx.fill();
				//Draw a box around the planet
				/*
				var radius = (I.width/2)*_scale;
				ctx.beginPath();
				ctx.rect(nX - radius - 5, nY - radius - 5, radius*2 + 10, radius*2 + 10);
				ctx.stroke();
				*/
				
				//draw center
				/*ctx.beginPath();
				ctx.arc(nX, nY, 1, 0, TWOPI);
				ctx.closePath();
				ctx.fillStyle = offScreenColor;
				ctx.fill();*/
			}
			if(_showMotionVector){
				//draw a line from the center of a planet showing the direction and speed it's travelling
				ctx.strokeStyle = motionColor;
				ctx.beginPath();
				ctx.moveTo(I.getScX(), I.getScY());
				ctx.lineTo(I.motion.x * _scale * 12 + I.getScX(), I.motion.y * _scale * 12 + I.getScY());
				ctx.stroke();
			}
		}
		//draw a semi-transparent planet where the cursor is
		if(!panning){
			var radius = newPlanetRadius * _scale;
			
			ctx.beginPath();
			ctx.arc(mouseX, mouseY, radius, 0, TWOPI);
			ctx.fillStyle = bodyColor;
			ctx.fill();
			
			ctx.lineStyle = "#FF0000";
			ctx.beginPath();
			ctx.rect(mouseX - radius - 5, mouseY - radius - 5, radius*2 + 10, radius*2 + 10);
			ctx.stroke();
		}
		
		//draw the planet you're about to launch
		if(mousePressed && dragging){
			ctx.fillStyle = bodyColor;
			ctx.lineStyle = trailColor;
			//draw a line from drag start to current mouse position
			ctx.beginPath();
			ctx.moveTo(getScX(dragStartX), getScY(dragStartY));
			ctx.lineTo(mouseX, mouseY);
			ctx.stroke();
			//draw a circle at the mouse position
			ctx.beginPath();
			ctx.arc(mouseX, mouseY, (draggedPlanet.width/2)*_scale, 0, TWOPI);
			ctx.fillStyle = bodyColor;
			ctx.fill();
		}
		drawInfo();
		setTimeout(redraw, 1000/60);
		
	}

	c.onmousedown = function(e){
		console.log(e);
		lastE = e;
		if(e.which != 1)return;
		mousePressed = true;
		mouseX = e.offsetX; //screen = screen
		mouseY = e.offsetY; //screen = screen
		if(mouseY < scHeight - infoBarHeight){
			dragStartX = getWorldX(mouseX); //world = getWorld(screen)
			dragStartY = getWorldY(mouseY); //world = getWorld(screen)
			if(!panning){
				draggedPlanet = new Body(newPlanetMass, dragStartX, dragStartY);
				dragging = true;
			}
		}
	}

	c.onmousemove = function(e){
		//mousePos(e);
		console.log(e);
		mouseX = e.offsetX; //screen = screen
		mouseY = e.offsetY; //screen = screen
		if(mousePressed && panning){
			scnOffset.x += ((mouseX - lastE.offsetX) / scale); //screen = screen - getScreen(world)
			scnOffset.y += ((mouseY - lastE.offsetY) / scale); //screen = screen - getScreen(world)
			//if recording
			if(isRecording){
				//if timestamp % 5 == 0
				if(gameTime % 5 == 0){
					//add pan event to recording
					recording.push(new Event('pan', gameTime, new Vector(scnOffset)));
				}
			}
		}
		else if(dragging){
			draggedPlanet.x = getWorldX(e.offsetX); //world = getWorld(screen)
			draggedPlanet.y = getWorldY(e.offsetY); //world = getWorld(screen)
		}
		lastE = e;
	}

	c.onmouseup = function(e){
		mousePressed = false;
		if(dragging){
			dragging = false;
			dragEndX = getWorldX(e.offsetX); //world = getWorld(screen)
			dragEndY = getWorldY(e.offsetY); //world = getWorld(screen)
			var pullLength = dist(dragStartX, dragStartY, dragEndX, dragEndY);
			var releaseMotion = new Vector((dragStartX - dragEndX)/12, (dragStartY - dragEndY)/12);
			bodies.push(new Body(draggedPlanet.mass, getWorldX(e.offsetX), getWorldY(e.offsetY), releaseMotion));
			
			//if recording
			if(isRecording){
				//add new planet event to recording
				recording.push(new Event('newPlanet', gameTime, lastBodyClone()));
			}
		}
	}

	
	
	seedScreen();
}

function mousePos(e){
	var thisx = e.offsetX;
	var thisy = e.offsetY;
	
	/*document.getElementById('screenCoord').innerHTML = "(" + thisx + ", " + thisy + ")";
	document.getElementById('worldCoord').innerHTML = "(" + getWorldX(thisx) + ", " + getWorldY(thisy) + ")";
	document.getElementById('offset').innerHTML = "(" + scnOffset.x + ", " + scnOffset.y + ")";*/
}


function seedScreen(){
	clearScreen();
	scnOffset = new Vector();
	
	//M, X, Y, new Vector(X, Y)
	/*//sun
	bodies.push(new Body(1.9891E30, 0, 0, new Vector(0, 0)));
	//mercury
	bodies.push(new Body(3.3022E23, 57910000, 0, new Vector(0, 47.87)));
	*/
	
	
	for(i = 0, sbc = startBodyCount; i < sbc; i++){
		bodies.push(new Body());
	}
	//avoid overlapping planets shooting off
	resolveCollisions();
	
	//if recording
	if(isRecording){
		//add seed screen event to recording
		recording.push(new Event('bookmark', gameTime, bodiesClone()));
	}
}

function togglePause(){
	paused = !paused;
	
	document.getElementById("pauseButton").innerHTML = (paused)? "Unpause" : "Pause";
}

function setPause(_p){
	paused = _p;
	document.getElementById("pauseButton").innerHTML = (paused)? "Unpause" : "Pause";
}

function togglePan(){
	panning = !panning;
	
	document.getElementById("panToggleButton").innerHTML = (panning)? "Launch Planet" : "Move camera";
	document.getElementById('messageBox').innerHTML = (panning)? "Click and drag to move the camera" : "Click and drag to launch a planet";
	document.getElementById('playArea').style.cursor = (panning)? "move" : "none";
}

function toggleTrails(){
	showTrails = !showTrails;

	document.getElementById("toggleTrails").innerHTML = (showTrails)? "Hide Trails" : "Show Trails";
	
	//if recording
	if(isRecording){
		//add trail toggle event to recording
		recording.push(new Event('trails', gameTime, showTrails));
	}
}

function toggleMotionVector(){
	showMotionVector = !showMotionVector;
	
	document.getElementById("toggleMotionVector").innerHTML = (showMotionVector)? "Hide Direction" : "Show Direction";
	
	//if recording
	if(isRecording){
		//add motion vector event to recording
		recording.push(new Event('motion', gameTime, showMotionVector));
	}
}

function clearScreen(){
	bodies = [];
	scnOffset = new Vector();
	
	//if recording
	if(isRecording){
		//add clear event to recording
		recording.push(new Event('bookmark', gameTime, bodiesClone()));
	}
}

function incLaunchSize(){
	if(newPlanetMass < 1024000){
		newPlanetMass *= 2;
		newPlanetRadius = Math.pow((Math.abs(newPlanetMass) * 3 / (4 * Math.PI)), 0.3333);
		document.getElementById('launchSize').innerHTML = newPlanetMass;
	}
}

function decLaunchSize(){
	if(newPlanetMass > 25){
		newPlanetMass /= 2;
		newPlanetRadius = Math.pow((Math.abs(newPlanetMass) * 3 / (4 * Math.PI)), 0.3333);
		document.getElementById('launchSize').innerHTML = newPlanetMass;
	}
}

function incScale(){
	if(scale < 10){
		var oldScale = scale;		
		scale *= 2;
		scnOffset.x -= ((1/scale) * viewWidth) * (oldScale / scale);
		scnOffset.y -= ((1/scale) * viewHeight) * (oldScale / scale);
		
		//if it's not an integer, show the first two decimals
		var percent = ((scale * 100) % 1 === 0)? scale * 100 : (scale * 100).toFixed(2);
		document.getElementById('scale').innerHTML =  percent + '%';		
		//document.getElementById('offset').innerHTML = "(" + scnOffset.x + ", " + scnOffset.y + ")";
		
		//if recording
		if(isRecording){
			//add zoom event to recording
			recording.push(new Event('zoom', gameTime, scale));
			recording.push(new Event('pan', gameTime, new Vector(scnOffset)));
		}	
	}
}

function decScale(){
	if(scale > 0.1){		
		scnOffset.x += viewWidth / (2 * scale);
		scnOffset.y += viewHeight / (2 * scale);
		
		scale /= 2;
		
		//if it's not an integer, show the first two decimals
		var percent = ((scale * 100) % 1 === 0)? scale * 100 : (scale * 100).toFixed(2);
		document.getElementById('scale').innerHTML =  percent + '%';
		//document.getElementById('offset').innerHTML = "(" + scnOffset.x + ", " + scnOffset.y + ")";
		
		//if recording
		if(isRecording){
			//add zoom event to recording
			recording.push(new Event('pan', gameTime, new Vector(scnOffset)));
			recording.push(new Event('zoom', gameTime, scale));
		}
	}
}

function drawInfo(){
	document.getElementById('planetCount').innerHTML = bodies.length;
	//if recording
	if(isRecording){
		if(gameTime % 30 == 0){
			showRecordingText = !showRecordingText;
		}
	}
	else{
		showRecordingText = false;
	}
	if(showRecordingText){
		//draw the word 'RECORDING' on the top-left corner of the screen
		ctx.font = "24px Arial";
		ctx.fillStyle = "#EEEE00";
		ctx.fillText("Recording", 15, 30);
	}
}

//keyPressed function

//mousePressed function

//mouseDragged function

//mouseReleased function

function resolveCollisions(){
	for(i = 0; i < bodies.length; i++){
		var I = bodies[i];
		for(c = 0; c < bodies.length; c++){
			if(c == i)continue;
			//if(bodies[i].mass == -1 || bodies[c].mass == -1)continue;
			
			var C = bodies[c];
			var distance = theDistance(I, C);
			if(distance >= I.width/2 + C.width/2)continue;
			
			var newMass = I.mass + C.mass;
			
			var newx = ((I.x * (I.mass / newMass)) + (C.x * (C.mass / newMass)));
			var newy = ((I.y * (I.mass / newMass)) + (C.y * (C.mass / newMass)));
			
			var newVx = ((I.motion.x * (I.mass / newMass)) + (C.motion.x * (C.mass / newMass)));
			var newVy = ((I.motion.y * (I.mass / newMass)) + (C.motion.y * (C.mass / newMass)));
			
			bodies.push(new Body(newMass, newx, newy, new Vector(newVx, newVy)));
			
			//remove bodies[i]
			var idx = bodies.indexOf(I);
			if(idx != -1)bodies.splice(idx, 1);
			//remove bodies[c]
			idx = bodies.indexOf(C);
			if(idx != -1)bodies.splice(idx, 1);
			break;
		}
	}
}

function updateBodiesAcc(){
	//copy in global variables
	var _G = G;
	for(i = 0, len = bodies.length; i < len; i++){
		var I = bodies[i];
		
		for(c = 0, cLen = bodies.length; c < cLen; c++){
			if(c != i){
				var C = bodies[c];
				var distan = theDistance(I, C);
				
				var vx = C.x - I.x;
				var vy = C.y - I.y;
				var magnitude = Math.sqrt(Math.pow(vx, 2) + Math.pow(vy, 2))
				var ux = vx / magnitude;
				var uy = vy / magnitude;
				var acc = (_G * C.mass) / Math.pow(distan, 2);
				
				bodies[i].motion.x += ux * acc;
				bodies[i].motion.y += uy * acc;
			}
		}
	}
}

function updateBodiesPos(){
	var i = bodies.length;
	while(i--){
		bodies[i].translate();
	}
}

//------------------------------
//BODY CLASS
function Body(/* nothing | Saved Body | M, X, Y | M, X, Y, init */){
	if(arguments.length == 0){
		this.mass = random(25, 150);
		this.x = random(0, viewWidth);
		this.y = random(0, viewHeight);
		//this.motion = new Vector(random(1)-0.5, random(1)-0.5);
		this.motion = new Vector();
	}
	else if(arguments.length == 1){
		this.mass = arguments[0].mass;
		this.x = arguments[0].x;
		this.y = arguments[0].y;
		this.motion = new Vector(arguments[0].motion.x, arguments[0].motion.y);
	}
	else if(arguments.length == 3){
		this.mass = arguments[0];
		this.x = arguments[1];
		this.y = arguments[2];
		this.motion = new Vector();
	}
	else if(arguments.length == 4){
		this.mass = arguments[0];
		this.x = arguments[1];
		this.y = arguments[2];
		this.motion = arguments[3];
	}
	this.trail = [];
	this.width = 2 * ( Math.pow((Math.abs(this.mass) * 3 / (4 * Math.PI)), 0.3333) );
	
	this.bodyColor = (this.mass >= 0)? "#BBBBBB" : "#AC1111";
}

Body.prototype.translate = function(){
	if(showTrails){
			this.trail.push(new Vector(this.x, this.y));
			if(this.trail.length > trailLength){
				this.trail.shift();
			}
		}
		else{
			this.trail = [];
		}
		this.x += this.motion.x;
		this.y += this.motion.y;
}

Body.prototype.getScX = function(){
	return (this.x + scnOffset.x) * scale;
}

Body.prototype.getScY = function(){
	return (this.y + scnOffset.y) * scale;
}
//END BODY CLASS
//------------------------------

//------------------------------
//VECTOR CLASS
function Vector(/* nothing | Vector | X, Y*/){
	if(arguments.length == 0){
		this.x = 0;
		this.y = 0;
	}
	else if(arguments.length == 1){
		this.x = arguments[0].x;
		this.y = arguments[0].y;
	}
	else if(arguments.length == 2){
		this.x = arguments[0];
		this.y = arguments[1];
	}	
}
//END VECTOR
//-------------------------------

function getScX(x){
	return (x + scnOffset.x) * scale;
}
function getScY(y){
	return (y + scnOffset.y) * scale;
}

function getWorldX(x){
	return (x / scale) - scnOffset.x;
}
function getWorldY(y){
	return (y / scale) - scnOffset.y;
}

function theDistance(a, b){
	return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}

function random(min, max){
	if(arguments.length == 1){
		return(Math.random()*arguments[0])+1;
	}
	return(Math.random()*(max-min))+(min + 1);
}

function dist(x1, y1, x2, y2){
	return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}


//VIDEO RECORDING FUNCTIONS

function startRecording(){
	//alert("started the recording");
	//create a variable in local storage
	//set the timestamp variable to 0
	gameTime = 0;
	//set the recording boolean to TRUE
	isRecording = true;
	recording = [];
	recording.push(new Event('bookmark', gameTime, bodiesClone()));
	//add a show trails event
	recording.push(new Event('trails', gameTime, showTrails));
	//add a show motion vector event
	recording.push(new Event('motion', gameTime, showMotionVector));
	//add a zoom event
	recording.push(new Event('zoom', gameTime, scale));
	//add a pan event
	recording.push(new Event('pan', gameTime, new Vector(scnOffset)));
}

function stopRecording(){
	//alert("stopped the recording");
	if(isRecording){
		recording.push(new Event('stop', gameTime, true));
	}
	//save the recording to local storage
	//set the timestamp variable to 0
	gameTime = 0;
	//set the recording boolean to FALSE
	isRecording = false;
	
	//let the user review and save the recording
	//send a JSON-encoded version of the recording to a PHP script for sanitizing and saving
	saveRecording();
}

function saveRecording(){
	alert('working');
	var xmlhttp;
	if(window.XMLHttpRequest){// code for IE7+, Firefox, Chrome, Opera, Safari
		xmlhttp = new XMLHttpRequest();
	}
	else{// code for IE6, IE5
		xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	}
	var params = "userid="+"1"+"&model="+JSON.stringify(recording)+"";
	xmlhttp.open("POST", "model/saveRecording.php", true);
	xmlhttp.send(params);
	xmlhttp.onreadystatechange = function(){
		if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
			alert("game saved");
			alert(xmlhttp.responseText);
		}
	}
}

function addRecordBookmark(){
	//alert("added bookmark");
	//save the current state in an event
	recording.push(new Event('bookmark', gameTime, bodiesClone()));
}

function Event(type, time, data){
	//TYPES: bookmark (includes initial state, reset, and clear), stop, zoom, pan, show motion vector, show trails, and new planet
	this.type = type;
	//game timestamp of the event
	this.time = time;
	//Data will be different based on which event type:
		//bookmark: an array of Body objects
		//stop: boolean value 'true'
		//zoom: new zoom ratio
		//pan: new screen offset
		//motion: boolean
		//trails: boolean
		//new planet: a Body
	this.data = data;
}

function bodiesClone(){
	var temp = [];
	for(i = 0; i < bodies.length; i++){
		temp.push(new Body(JSON.parse(JSON.stringify(bodies[i]))));
	}
	return temp;
}

function lastBodyClone(){
	return new Body(JSON.parse(JSON.stringify(bodies[bodies.length - 1])));
}
